/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"gitee.com/extrame/fb-runner/ca"
	"gitee.com/extrame/fb-runner/channel"
	"gitee.com/extrame/fb-runner/cryptogen"
	"gitee.com/extrame/fb-runner/docker"
	"gitee.com/extrame/fb-runner/peer"
	"gitee.com/extrame/fb-runner/store"
	"gitee.com/extrame/fb-runner/util"
	"gitee.com/extrame/fb-runner/version"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

var forceRecreate *bool

// upCmd represents the up command
var upCmd = &cobra.Command{
	Use:   "up",
	Short: "启动网络环境",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		var append string
		if cfg.Ca {
			append = " and using ca"
		}
		logrus.Infof("Starting nodes with CLI timeout of '%d' tries and CLI delay of '%d' seconds and using database '%s'"+append, cfg.MaxRetry, cfg.CliDelay, cfg.Database)

		checkPrereqs()

		if *forceRecreate || cfg.BaseIsNotExists() {
			store.FSM.Reset()
		}
		store.FSM.Check(func() error {
			if cfg.Ca {
				return createCa(cfg.GetCv())
			}
			return nil
		}) // step 1
		store.FSM.Check(runCaIfIs, true)  // step 2
		store.FSM.Check(createOrgs)       // step 3
		store.FSM.Check(createConsortium) //step 4
		store.FSM.Check(createDockerFile)
		//generate artifacts if they don't exist

		logrus.Info("start docker fabric")

		err := docker.Run(cfg.GetBase(), cfg.NetwokName, strings.ToLower(cfg.Database) == "couchdb")

		if err != nil {
			logrus.Fatalln("start network fail", err)
		}
	},
}

func init() {
	rootCmd.AddCommand(upCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// upCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	forceRecreate = upCmd.Flags().BoolP("force", "F", false, "force to recreate organization config files")
}

func checkPrereqs() {

	p_version, err := peer.Version()

	if err != nil || p_version == "" {
		logrus.Errorln("Peer binary not found..", err)
		logrus.Errorln("Follow the instructions in the Fabric docs to install the Fabric Binaries:")
		logrus.Fatalln("https://hyperledger-fabric.readthedocs.io/en/latest/install.html")
	}

	if p_version == "" {
		logrus.Errorf("Peer version %s is not supported", p_version)
		logrus.Errorln("Follow the instructions in the Fabric docs to install the Fabric Binaries:")
		logrus.Fatalln("https://hyperledger-fabric.readthedocs.io/en/latest/install.html")
	}

	if !peer.CheckConfig(cfg.Config()) {
		logrus.Fatalln("源文件夹中没有config文件夹")
	}

	var d_version, d_ca_version version.Version

	logrus.Infoln("check docker version...")

	d_version, err = docker.Version()

	if err != nil {
		logrus.Fatalln(err.Error())
	}

	logrus.Infof("LOCAL_VERSION=%s", p_version)
	logrus.Infof("DOCKER_IMAGE_VERSION=%s", d_version)

	if p_version != d_version {
		logrus.Errorln("Local fabric binaries and docker images are out of  sync. This may cause problems.")
	}

	if !p_version.IsSupported() {
		logrus.Fatalf("Local Fabric binary version of $s does not match the versions supported by the test network.", p_version)
	}

	if !d_version.IsSupported() {
		logrus.Fatalf("Fabric Docker image version of $s does not match the versions supported by the test network.", d_version)
	}

	//   ## Check for fabric-ca
	if cfg.Ca {

		ca_version, err := ca.Version()
		if err != nil || ca_version == "" {
			logrus.Errorln("fabric-ca-client binary not found..", err)
			logrus.Errorln("Follow the instructions in the Fabric docs to install the Fabric Binaries:")
			logrus.Errorln("https://hyperledger-fabric.readthedocs.io/en/latest/install.html")
			os.Exit(1)
		}

		d_ca_version, err = docker.CaVersion()

		if err != nil || d_ca_version == "" {
			logrus.Errorln("fabric-ca-client image not found..", err)
			logrus.Errorln("Follow the instructions in the Fabric docs to install the Fabric Binaries:")
			logrus.Errorln("https://hyperledger-fabric.readthedocs.io/en/latest/install.html")
			os.Exit(1)
		}

		logrus.Infof("CA_LOCAL_VERSION=%s", ca_version)
		logrus.Infof("CA_DOCKER_IMAGE_VERSION=%s", d_ca_version)

		if ca_version != d_ca_version {
			logrus.Warnln("Local fabric-ca binaries and docker images are out of sync. This may cause problems.")
		}
	}
	cfg.SetVersion(d_version, d_ca_version)
	return
}

func createCa(v version.Version) error {
	logrus.Infoln("Generating certificates using Fabric CA")

	return ca.Create(v, cfg.GetSource(), cfg.GetBase(), cfg.NetwokName)
}

func runCaIfIs() error {
	if cfg.Ca && util.Config.HasCaInThisMachine() {
		return ca.Run(cfg.GetBase(), cfg.NetwokName)
	}
	return nil
}

func createOrgs() error {

	if !cfg.Ca {

		logrus.Infoln("Generating certificates using cryptogen tool")

		for _, o := range cfg.Organizations {

			logrus.Infoln("Creating " + o.GetName() + " Identities")
			var err error
			if o.IsOrderer {
				err = cryptogen.Generate(o, filepath.Join(cfg.GetSource(), "cryptogen/crypto-config-orderer.yaml"), filepath.Join(cfg.GetBase(), "organizations"))
			} else {
				err = cryptogen.Generate(o, filepath.Join(cfg.GetSource(), "cryptogen/crypto-config-peer.yaml"), filepath.Join(cfg.GetBase(), "organizations"))
			}
			return err
		}

	} else {

		for k, org := range cfg.Organizations {
			logrus.Infoln("Creating " + k + " Identities")
			err := ca.CreateOrg(cfg.GetBase(), org)
			if err != nil {
				logrus.Fatalln("create identities for "+k+" fail", err)
			}
		}

	}
	return nil
}

// TODO support 2.3
// TODO 判断严格，有可能host没有配置ismaster
func createConsortium() error {
	if cfg.IsMaster(store.GetLocalId()) {
		var fName = filepath.Join(cfg.GetBase(), "system-genesis-block/genesis.block")
		util.MkdirFor(fName)
		var cfgDir = filepath.Join(cfg.GetBase(), "config")
		util.Mkdir(cfgDir)
		exec.Command("cp", filepath.Join(cfg.GetSource(), "config", "core.yaml"), cfgDir+"/").Run()
		exec.Command("cp", filepath.Join(cfg.GetSource(), "config", "orderer.yaml"), cfgDir+"/").Run()
		err := channel.GenConfigTx(cfg.GetSource(), cfg.GetBase())
		if err == nil {
			cmd := exec.Command("configtxgen", "-profile", "TwoOrgsOrdererGenesis", "-channelID", "system-channel", "-outputBlock", fName, "-configPath", cfgDir)
			_, _, err = util.Run(cmd, true)
		}
		return err
	}
	return nil
}

func createDockerFile() error {
	return docker.Create(cfg.GetDv(), cfg.GetSource(), cfg.GetBase(), cfg.NetwokName, strings.ToLower(cfg.Database) == "couchdb")
}
